package EcoSpeed;

import java.util.*;
import java.util.Vector;


/**
 * Class SmartSystem
 * Coeur de calcul.
 * Reçois les demandes d'ascenseur. (Observer) (newRequest)
 * Reçois les informations de l'alarme. (Observer) (alarm)
 * Reçois les informations quand un ascenseur a fini son trajet. (Observer)
 * (endRequest)
 * Reçois les tic du TimeLine. (updateZone)
 * Demande aux ascenseurs de se déplacer. (moveLift)
 * Donne aux afficheurs la valeur à afficher. (printValue)
 */
public class SmartSystem implements Observer {
	/**
	 * Es la 1er mise a jour ?
	 */
	private boolean firstUpdate = true;

    /**
     * Objet de manipulation des statistiques.
     */
    private Statistics stats;

    /**
     * Vecteur des écrans de saisie et de visualisation
     * des étages.
     */
    private Vector<Screen> screens;

    /**
     * Vecteur des ascenseurs.
     */
    private Vector<Lift> lifts;

    /**
     * Vecteur stockant les zones de positionnement
     * des ascenseurs.
     */
    private Vector<Zone> zones;
    /**
     * Constructeur
     * @param driversLiftList Liste des drivers
     * @param screens Liste des Ecrans
     */
    public SmartSystem (Vector driversLiftList,Vector screens) {
        if (	driversLiftList.isEmpty()) {
            throw new NullPointerException("Impossible d'initialiser le SmartSystem avec une liste vide de driversLift.");
        }

        // Instanciation des objets.
        stats = new Statistics();
        this.screens = screens;
        lifts = new Vector<Lift>();
        zones = new Vector<Zone>();

        // Création des ascenseurs.
        Iterator it = driversLiftList.iterator();
        EcoSpeed.DriverLift lift;
        while (it.hasNext()) {
            lift = (EcoSpeed.DriverLift) it.next();
            lifts.add(new Lift(lift));
        }
    };

    /**
     * Accesseur du tableau de zones
     * @return la valeur de zones
     */
    public Vector<Zone> getZones() {
        return this.zones;
    }

    /**
     * Toutes les heures il faut changer les zones en fonction des stats
     */
    public void updateZones() {
        int size = zones.size();
        if (!this.firstUpdate) {
            this.stats.newHour();
        }
        this.firstUpdate = false;
        // Récupération de la liste des étages découpant les zones.
        int[] NCileResult = stats.nCile(lifts.size());

        // Récupération du nombre de zones
        int nbZones = NCileResult.length;

        // Si le nombre de zones est supérieur au nombre stocké
        // on ajoute les nouvelles zones.
        if (nbZones > size) {
            for (int i = size; i < nbZones; i++) {
                zones.add(new Zone());
            }
        } // Si le nombre de zones est inférieur au nombre stocké
        // on supprime les dernières zones.
        else if (nbZones < size) {
            for (int i = nbZones; i < size; i++) {
                zones.remove(zones.lastElement());
            }
        }

        // Je parcours chaque zone et je la met à jour.
        for (int i = 0; i < nbZones; i++) {
            // Si c'est la première zone, le début est à 0, sinon i-1
            int startFloor = i == 0 ? 0 : NCileResult[i - 1];
            int endFloor = NCileResult[i];
            int bestFloor = stats.floorForZone(startFloor, endFloor);
            // Mise à jour du début et de la fin de la zone.
            // Mise à jour de la position optimale.
            zones.get(i).updateZone(startFloor, endFloor, bestFloor);
        }
        this.checkZonesCoverage();
    }

    /**
     * Methode apellée quand un clavier effectue une demande :
     * - Recherche du meilleur ascenseur : le plus près sur le chemin, sinon celui qui s'arrete
     * le moins loin
     * - On achemine le meilleur ascenseur à l'étage de la demande
     * - Affichage de l'ascenseur sur l'écran
     * @param screen Ecran d'affichage de la nouvelle requete
     * @param startFloor Etage de debut
     * @param endFloor Etage destination
     * @param urgent C'est urgent ?
     */
    public void newRequest(Screen screen, int startFloor, int endFloor, boolean urgent) {
        /* On choisit le meilleur ascenseur pour cette nouvelle demande*/
        Lift bestLift = this.selectLift(startFloor, endFloor, urgent);
        /* MAJdes starts */
        stats.newRequest(startFloor);
        /* Si c'est une urgence, on purge les demandes de l'ascenseur */
        if (urgent) {
            bestLift.clearTarget();
        }
        /*ajout un nouvel étage à la liste des étages déservis */
        bestLift.setUrgence(urgent);
        bestLift.addStop(startFloor, endFloor);
        /* Affichage du bestLift à l'écran de l'étage de la demande */
        screen.printf(lifts.indexOf(bestLift));
        /*Verification de la couverture des zone */
        this.checkZonesCoverage();
    }

    /**
     * Methode quand l'alarme change d'etat.
     * @param        value
     */
    public void alarm(Boolean value) {
        Iterator itr = this.lifts.iterator();
        if (value) {//Si l'alarme vient d'être déclencher
            //On arrête tous les ascenseurs au prochain
            while (itr.hasNext()) {
                Lift l = (Lift)itr.next();
                int stopFloor = l.getFloor() + l.getState();
                l.clearTarget();
                l.addStop(stopFloor,stopFloor);
            }
        } else { //Si l'alarm vient de s'arreter
            //On envoye tous les ascenseurs au 0
            while (itr.hasNext()) {
                int groundFloor = (Utils.NB_UNDERGROUND);
                ((Lift)itr.next()).addStop(groundFloor, groundFloor);
            }
        }
    }

    /**
     * Verifie la couverture des zones de l'immeuble
     */
    public void checkZonesCoverage(  ) {

	Vector<Zone> tmpZones = new Vector<Zone>();
	Vector<Lift> tmpLifts = new Vector<Lift>();
	Iterator it, it2;
	Zone z;
	Lift l,lForZone;
	int pathLength1,pathLength2;
	
	/*Remplisage des vecteur temporaire */
	it=this.zones.iterator();
	while(it.hasNext()){
		tmpZones.add((Zone)it.next());
	}
	it=this.lifts.iterator();
	while(it.hasNext()){
		tmpLifts.add((Lift)it.next());
	}
	
	/*Match des ascenseurs par zones*/
	for(int i=0;i<tmpZones.size();){
		/*Pour chaque Zone*/
		z = tmpZones.get(i);
		lForZone = null;
		l = tmpLifts.get(0);
		if(l.getState() == Lift.NO_MOVE){
		    pathLength1 = l.getPathLength(z.getBestFloor(),z.getBestFloor(),1);
		}
		else{
		    pathLength1 = l.getPathLength(l.getFinalFloor(),z.getBestFloor(),1);
		}

		it = tmpLifts.iterator();
		while(it.hasNext()){
			l = (Lift)it.next();
			if(l.getState() == Lift.NO_MOVE){
			    pathLength2 = l.getPathLength(z.getBestFloor(),z.getBestFloor(),1);
			}
			else{
			    pathLength2 = l.getPathLength(l.getFinalFloor(),z.getBestFloor(),1);
			}
			if(z.getStartFloor()<=l.getFinalFloor() && z.getEndFloor() >= l.getFinalFloor()){
			    if((lForZone == null || pathLength1 > pathLength2)){
				pathLength1 = pathLength2;
				lForZone  = l;
			    }
			}
		}
		if(z.getBestFloor() == 42){
			z.getBestFloor();
		}
		if(lForZone!=null && (lForZone.getState() != Lift.NO_MOVE || lForZone.getFinalFloor() != z.getBestFloor())){
			lForZone.addStop(z.getBestFloor(),z.getBestFloor());
			tmpLifts.remove(lForZone);
			tmpZones.remove(z);
		}
		else{
			i++;
		}
	}

	/*Match des ascenseurs pour les zone restantes */
	while(tmpZones.size()>0){
		z = tmpZones.get(0);
		it = tmpLifts.iterator();
		lForZone = tmpLifts.get(0);
		if(lForZone.getState() != lForZone.getRequestState(lForZone.getFinalFloor(), z.getBestFloor())){
		    pathLength1 = lForZone.getPathLength(z.getBestFloor(),z.getBestFloor(),1);
		}
		else{
		    pathLength1 = lForZone.getPathLength(lForZone.getFinalFloor(),z.getBestFloor(),1);
		}
		while(it.hasNext()){
			l = (Lift)it.next();
			pathLength2 = l.getPathLength(z.getBestFloor(),z.getBestFloor(),1);
			if(pathLength1 > pathLength2 ){
				lForZone = l;
				pathLength1 = pathLength2;
			}
		}
		if(lForZone.getFinalFloor() != z.getBestFloor()){
			lForZone.addStop(z.getBestFloor(), z.getBestFloor());
		}
		tmpLifts.remove(lForZone);
		tmpZones.remove(z);
	}
    }

    /**
     * Methodes qui selectionne le meilleur ascenseur pour aller de @startFloor à
     * @endFloor
     * @return       EcoSpeed.Lift
     * @param        startFloor Etage de la demande
     * @param        endFloor Etage destination
     * @param urgent C'est urgent ?
     */
    public EcoSpeed.Lift selectLift( int startFloor, int endFloor, boolean urgent ) {

        int bestPathLength = Integer.MAX_VALUE;
        Iterator itr;
	itr = this.lifts.iterator();
	Lift bestLift = null;
	while(itr.hasNext() && bestLift == null){
		Lift l = (Lift)itr.next();
		if(!l.getUrgence()){
			bestLift = l;
		}
	}
	if(bestLift == null){
		//Tous les ascenseurs sont en urgence
		return null;
	}
        if (urgent) {
            int tmpBestFloor = Math.abs(startFloor-Utils.NB_FLOORS);
	    itr = this.lifts.iterator();
            while (itr.hasNext()) {
                Lift l = (Lift)itr.next();
                int currentFloor = l.getFloor();
                if (!l.getUrgence() && Math.abs(startFloor-currentFloor) < Math.abs(startFloor-tmpBestFloor)) {
                    tmpBestFloor = currentFloor;
                    bestLift = l;
                }
            }
            bestLift.clearTarget();
        } else {
	    bestPathLength = this.lifts.get(0).getPathLength(startFloor, endFloor,2);
	    itr = this.lifts.iterator();
            while (itr.hasNext()) {
                Lift l = (Lift)itr.next();
                int currentPathLength = l.getPathLength(startFloor, endFloor,2);
                if ( currentPathLength < bestPathLength) {
		    bestPathLength = currentPathLength;
                    bestLift = l;
                }
            }
        }
        return bestLift;
    }

    /**
     * Accesseur récupérant la liste des ascenseurs.
     * @return les ascenseurs
     */
    public Vector<Lift> getLifts() {
        return lifts;
    }




    /**
     * Mise a jour du pattern Observer
     * @param arg0 l'objet qui est observé
     * @param arg1 les arguments
     */
    public void update(Observable arg0, Object arg1) {
	if (arg0 instanceof DriverKeyboard) {
            /* Si c'est un clavier (nouvelle demande) */
            //Recuperation du clavier
            DriverKeyboard keyboard = (DriverKeyboard)arg0;
            //Recuperation du message
            NotificationMessage mes = (NotificationMessage)arg1;
            //Selection de l'ecran de ce clavier
            Iterator it = this.screens.iterator();
            Screen tmp;
            boolean end = false;
            while (it.hasNext() && !end) {
                tmp = (Screen)it.next();
                if (tmp.getId() == keyboard.getId()) {
                    //Envoi de la demande
                    this.newRequest(tmp, mes.currentFloor, mes.targetFloor, mes.urgent);
                    end = true;
                }
            }
        }
	else if (arg0 instanceof Alarme){
		// Si l'alarme a changé d'état
		Alarme alarm = (Alarme)arg0;
		this.alarm(alarm.getStatut());	}
	else {
            throw new UnsupportedOperationException("Not supported yet.");
        }
    }

    /**
     * Ascesseur des statistics
     * @return les statistics
     */
    public Statistics getStats(){
	    return this.stats;
    }

    /**
     * Changement de la date courante
     * @param d la nouvelle date
     */
    public void setDate(Date d){
	    this.stats.setDate(d);
    }
    /**
     * Réinitialise le SmartSystem
     */
    public void reset(){
	    Iterator it = this.lifts.iterator();
	    Lift l;
	    while(it.hasNext()){
		    l = (Lift)it.next();
		    l.reset();
	    }

    }

}
